package corpo;

import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import excecoes.ItemNaoContidoException;

/**
 * Classe respons�vel por gerenciar as informa��es relevantes (nome, mapa de prefer�ncias e data de emiss�o das prefer�ncias) de um usu�rio no reposit�rio do programa.
 * 
 * @author Pedro Augustus Diniz Falc�o Silva
 *
 */
public class Usuario {
	private String nome;
	private HashMap<Estabelecimento, QualidadeEstabelecimento> estabelecimentosENotas;
	private Date dataVotacao;
	
	/**
	 * Construtor padr�o da classe, cria um novo Objeto Usuario a partir do nome passado como par�metro. O mapa de estabelecimentos e notas � instanciado como vazio e a data das opini�es do usu�rio � configurada como a data atual do sistema.
	 * @param nome O nome do usu�rio
	 */
	public Usuario(String nome) {
		this.setNome(nome);
		setMapaDeInteiros(new HashMap<Estabelecimento, Integer>());
		setDataVotacao(new Date());
	}
	
	/**
	 * Cria um novo Objeto Usuario a partir do nome e do mapa (de estabelecimentos e notas) passado como par�metro. A data � configurada como a data atual do sistema.
	 * @param nome O nome do usu�rio
	 * @param estabelecimentosENotas Um HashMap<Estabelecimento, Integer> que guarda a opini�o de cada usu�rio sobre o estabelecimento.
	 */
	public Usuario(String nome, HashMap<Estabelecimento, Integer> estabelecimentosENotas) {
		this.setNome(nome);
		this.setMapaDeInteiros(estabelecimentosENotas);
		this.setDataVotacao(new Date());
	}
	
	/**
	 * Cria um Objeto Usuario a partir dos tr�s par�metros passados. Os par�metros s�o uma String, um HashMap<Estabelecimento, Integer> e um GregorianCalendar.
	 * @param nome O nome do usu�rio.
	 * @param estabelecimentosENotas Um HashMap<Estabelecimento, Integer> que guarda a opini�o de cada usu�rio sobre o estabelecimento.
	 * @param dataVotacao A data na qual a opini�o do usu�rio foi emitida.
	 */
	public Usuario(String nome, HashMap<Estabelecimento, Integer> estabelecimentosENotas, Date dataVotacao) {
		this.setNome(nome);
		this.setMapaDeInteiros(estabelecimentosENotas);
		this.setDataVotacao(dataVotacao);
	}
	
	/**
	 * Constr�i um objeto Usuario a partir dos parametros passados. A diferen�a para os construtores anteriores � o tipo de valores do mapa.
	 * @param estabelecimentosENotas Um mapa contendo a opini�o do usu�rio sobre cada um dos estabelecimentos, mas dessa vez em forma de objeto QualidadeEstabelecimento.
	 * @param nome O nome do usu�rio
	 */
	public Usuario(HashMap<Estabelecimento, QualidadeEstabelecimento> estabelecimentosENotas, String nome) {
		this.setNome(nome);
		this.setMapaDeQualidade(estabelecimentosENotas);
		this.setDataVotacao(new Date());
	}
	
	/**
	 * Constr�i um objeto Usuario a partir dos parametros passados. A diferen�a para os construtores anteriores � o tipo de valores do mapa.
	 * @param estabelecimentosENotas Um mapa contendo a opini�o do usu�rio sobre cada um dos estabelecimentos, mas dessa vez em forma de objeto QualidadeEstabelecimento.
	 * @param nome O nome do usu�rio
	 * @param dataVotacao A data de emiss�o das prefer�ncias do usu�rio.
	 */
	public Usuario(HashMap<Estabelecimento, QualidadeEstabelecimento> estabelecimentosENotas, String nome, Date dataVotacao) {
		this.setNome(nome);
		this.setMapaDeQualidade(estabelecimentosENotas);
		this.setDataVotacao(dataVotacao);
	}

	/**
	 * Altera o nome do usu�rio para a String passada como par�metro.
	 * @param nome O novo nome do usu�rio.
	 */
	public void setNome(String nome) {
		this.nome = nome;
	}

	/**
	 * Retorna o nome do usu�rio.
	 * @return O nome do usu�rio.
	 */
	public String getNome() {
		return nome;
	}
	
	/**
	 * Retorna um conjunto com todos os estabelecimentos atualmente presentes no mapa do usuario.
	 * @return Um conjunto com os estabelecimentos presentes no mapa do usuario.
	 */
	public Set<Estabelecimento> getEstabelecimentos() {
		Set<Estabelecimento> novo = this.estabelecimentosENotas.keySet();
		return novo;
	}

	/**
	 * Altera o mapa de prefer�ncias do usu�rio para o passado como par�metro sem alterar a data de emiss�o.
	 * @param estabelecimentosENotas O novo mapa contendo os valores de prefer�ncia do usu�rio.
	 */
	public void setMapaDeInteiros(HashMap<Estabelecimento, Integer> estabelecimentosENotas) {
		HashMap<Estabelecimento,QualidadeEstabelecimento> eq = new HashMap<Estabelecimento,QualidadeEstabelecimento>();
		
		for (Estabelecimento est : estabelecimentosENotas.keySet()) {
			QualidadeEstabelecimento valor = QualidadeEstabelecimento.toQualidade(estabelecimentosENotas.get(est));
			eq.put(est, valor);
		} this.estabelecimentosENotas = eq;
	}

	/**
	 * Altera os valores de prefer�ncias do usu�rio e data da emiss�o para os passados como par�metro.
	 * @param estabelecimentosENotas O novo mapa contendo os valores de prefer�ncia do usu�rio.
	 * @param dataVotacao A data de emiss�o das prefer�ncias do usu�rio.
	 */
	public void setMapaDeQualidade(HashMap<Estabelecimento, QualidadeEstabelecimento> estabelecimentosENotas) {
		this.estabelecimentosENotas = estabelecimentosENotas;
	}
	
	/**
	 * Configura a data de emiss�o das prefer�ncias do usu�rio para a data atual do sistema.
	 */
	public void setDataVotacao() {
		this.dataVotacao = new Date();
	}

	/**
	 * Configura a data de emiss�o das prefer�ncias do usu�rio para a data passada como par�metro.
	 * @param dataVotacao A data de emiss�o das prefer�ncias do usu�rio.
	 */
	public void setDataVotacao(Date dataVotacao) {
		this.dataVotacao = dataVotacao;
	}

	/**
	 * Retorna a data de emiss�o das prefer�ncias do usu�rio.
	 * @return A data de emiss�o das prefer�ncias do usu�rio.
	 */
	public Date getDataVotacao() {
		return dataVotacao;
	}
	
	/**
	 * Retorna um Objeto QualidadeEstabelecimento correspondente ao valor da chave passada como parametro de acordo com o mapa do usuario. As chaves do mapa todas s�o Objetos Estabelecimento.
	 * @param est A chave a ser pesquisada.
	 * @return O valor da chave passada no mapa do usuario.
	 */
	public QualidadeEstabelecimento getQualidade(Estabelecimento est) {
		if (!this.estabelecimentosENotas.containsKey(est)) {
			throw new ItemNaoContidoException("Item nao contido no mapa!", Estabelecimento.class.getName(), est.getNome());
		} else {
			return this.estabelecimentosENotas.get(est);
		}
	}
	
	/**
	 * Tenta adicionar ao mapa do usuario cada Estabelecimento da colecao passada como parametro. Caso o mapa ja possua o estabelecimento, este eh ignorado. Caso o mapa nao contenha o Estabelecimento, este eh adicionado com o valor QualidadeEstabelecimento.DESCONHECIDO .
	 * @param col A colecao de estabelecimentos a serem adicionados.
	 */
	public void addEstabelecimentos(Collection<Estabelecimento> col) {
		for (Estabelecimento est : col) {
			if (!this.estabelecimentosENotas.containsKey(est)) {
				this.estabelecimentosENotas.put(est, QualidadeEstabelecimento.DESCONHECIDO);
			}
		}
	}
	
	/**
	 * Retorna um boolean que conrfima se o usu�rio em quest�o conhece ou n�o o estabelecimento, ou lan�a uma exce��o ItemNaoContidoException se o estabelecimento em quest�o n�o estiver contido no mapa de estabelecimentos do usu�rio.
	 * @param e O estabelecimento a ser pesquisado
	 * @return 'true' se o valor de avalia��o do estabelecimento no mapa do usu�rio for diferente de 0 (Desconhecido).
	 * @throws ItemNaoContidoException Caso o estabelecimento passado n�o esteja contido no mapa do usu�rio.
	 */
	public boolean conhece(Estabelecimento e) throws ItemNaoContidoException {
		if (e == null) {
			throw new IllegalArgumentException();
		}
		if (estabelecimentosENotas == null) {
			throw new IllegalArgumentException();
		}
		if (!this.estabelecimentosENotas.containsKey(e)) {
			this.estabelecimentosENotas.put(e, QualidadeEstabelecimento.DESCONHECIDO);
			return false;
		} else {
			if (this.estabelecimentosENotas.get(e).toInt() != 0) {
				return true;
			} return false;
		}
	}
	
	/**
	 * Retorna um boolean que confirma se o usu�rio gostou ou n�o do estabelecimento (se o valor atribu�do ao estabelecimento no mapa do usu�rio for maior que 0), ou retorna uma exce��o caso o estabelecimento em quest�o n�o estiver contido no mapa do usu�rio.
	 * @param e O estabelecimento a ser pesquisado.
	 * @return 'true' caso a qualidade que o usu�rio atribuiu ao estabelecimento seja superior a 0.
	 * @throws ItemNaoContidoException Caso n�o exista o estabelecimento em quest�o como chave do mapa do usu�rio.
	 */
	public boolean gosta(Estabelecimento e) throws ItemNaoContidoException {
		if (!this.estabelecimentosENotas.containsKey(e)) {
			throw new ItemNaoContidoException("O estabelecimento nao esta contido no mapa deste usuario!", Estabelecimento.class.getSimpleName(), e.getNome());
		} else {
			if (this.estabelecimentosENotas.get(e).toInt() > 0) {
				return true;
			} return false;
		}
	}
	
	/**
	 * Compara os dois usu�rios, retornando um inteiro que aponta a intensidade da semelhan�a entre os dois usu�rios, quanto maior o inteiro, mais avalia��es em comum os usu�rios t�m. Para chegar a tal resultado, � checado se ambos os usu�rios conhecem o estabelecimento X, se ambos os usu�rios concordam em aquele estabelecimento ser bom ou ruim, adiciona-se 1 "ponto de semelhan�a". � subtra�do 1 "ponto de semelhan�a" caso os usu�rios discordem.
	 * @param user O usu�rio a ser comparado.
	 * @return A quantidade de "pontos de semelhan�a" entre os dois usu�rios.
	 */
	public int comparaUsuarios(Usuario user) {
		int pontosDeSemelhanca = 0;
		
		for (Estabelecimento est : this.estabelecimentosENotas.keySet()) { // Para cada estabelecimento no mapa do usuario
			if (est != null && user.conhece(est)) { // Se o outro usuario conhece o estabelecimento tamb�m
				if (user.gosta(est) && this.gosta(est)) { // Se ambos gostam
					pontosDeSemelhanca++;
				} else if (!user.gosta(est) && !this.gosta(est)) { // Se ambos NAO gostam
					pontosDeSemelhanca++;
				} else { // Se um gosta e o outro nao
					pontosDeSemelhanca--;
				}
			}
		}
		return pontosDeSemelhanca;
	}
	
	/**
	 * Retorna um conjunto com todos os Estabelecimentos aos quais o usuario atribuiu uma nota acima de 0.
	 * @return um conjutno de estabelecimentos aprovados pelo usuario.
	 */
	public Set<Estabelecimento> getEstabelecimentosAprovados() {
		Set<Estabelecimento> retorno = new HashSet<Estabelecimento>();
		for (Estabelecimento est : this.getEstabelecimentos()) {
			if (this.gosta(est)) {
				retorno.add(est);
			}
		} return retorno;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		String pulaLinha = System.getProperties().getProperty("line.separator");
		String toString = "Usuario " + this.getNome() + pulaLinha;
		
		try {
			toString = toString.concat("Opinioes (total: " + this.estabelecimentosENotas.size() + "): " + pulaLinha);
		} catch (Exception e) {
			System.out.println(e.getCause().getLocalizedMessage());
		}
		for (Estabelecimento e : this.estabelecimentosENotas.keySet()) {
			toString = toString.concat(" - " + e.getNome() + " : " + this.estabelecimentosENotas.get(e) + pulaLinha);
		}
		return toString;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((dataVotacao == null) ? 0 : dataVotacao.hashCode());
		result = prime
				* result
				+ ((estabelecimentosENotas == null) ? 0
						: estabelecimentosENotas.hashCode());
		result = prime * result + ((nome == null) ? 0 : nome.hashCode());
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Usuario)) {
			return false;
		}
		Usuario outro = (Usuario) obj;
		if (dataVotacao == null) {
			if (outro.dataVotacao != null) {
				return false;
			}
		} else if (!dataVotacao.equals(outro.dataVotacao)) {
			return false;
		}
		if (estabelecimentosENotas == null) {
			if (outro.estabelecimentosENotas != null) {
				return false;
			}
		} else if (!estabelecimentosENotas.equals(outro.estabelecimentosENotas)) {
			return false;
		}
		if (nome == null) {
			if (outro.nome != null) {
				return false;
			}
		} else if (!nome.equals(outro.nome)) {
			return false;
		}
		return true;
	}
	
	
}